"""
Basic widgets for VN Trader.
"""

import csv
import threading
from copy import copy
from datetime import datetime
from enum import Enum
from typing import Any, Dict, List

from PySide6 import QtCore, QtGui, QtWidgets
from PySide6.QtCore import QTimer, pyqtSignal

from entity.event import Event, EventEngine
from entity.event import (
    EVENT_TICK,
    EVENT_TRADE,
    EVENT_ORDER,
    EVENT_POSITION,
    EVENT_POSITION_DETAIL,
    EVENT_ACCOUNT,
    EVENT_LOG
)
from entity.constant import Direction, Exchange, Offset, OrderType
from entity.engine import MainEngine
from entity.objects import OrderRequest, SubscribeRequest
from entity.setting import SETTING_FILENAME, SETTINGS
from pyutils.dir_file import load_json, save_json, get_digits

COLOR_LONG = QtGui.QColor("red")
COLOR_SHORT = QtGui.QColor("green")
COLOR_BID = QtGui.QColor(255, 174, 201)
COLOR_ASK = QtGui.QColor(160, 255, 160)
COLOR_BLACK = QtGui.QColor("black")


#  region of boxi added


class StatusComboBox(QtWidgets.QComboBox):
    """状态栏的 ComboBox 单元格"""

    def __init__(self, name, label=None, data_event_engine=None, event_type=None, data_member=None, visible=True):
        """

        :param name: 控件名称
        :param label: 显示固定部分
        :param data_event_engine: 引起显示可变部分事件引擎，如果为None,仅显示固定部分
        :event_type: 事件类型
        """
        super(StatusComboBox, self).__init__()
        self.name = name
        self.setObjectName(name)
        self.label = label
        self.data_event_engine = data_event_engine
        self.event_type = event_type
        self.data_member = data_member
        self.setVisible(visible)
        self.register()
        self.setEditable(False)
        self.setSizeAdjustPolicy(self.SizeAdjustPolicy.AdjustToContents)
        self.old_width = 0
        self.old_min_width = self.minimumWidth()
        # self.resize(100, 30)
        # print('StatusComboBox height:', self.height())

    def register(self):
        if self.data_event_engine and self.event_type:
            self.data_event_engine.register(self.event_type, self.update_event)

    def update_event(self, event):
        data = event.data
        label = self.label if self.label else datetime.now().strftime('%Y%m%d %H:%M:%S')
        if self.data_member:
            self.addItem(':'.join([label, data.__dict__[self.data_member]]))
        else:
            self.addItem(':'.join([label, data]))

    # ########以下为QComboBox 部分事件重载
    def activated(self, index: int):
        # print(self.width())
        self.old_width = self.width()
        self.old_min_width = self.minimumWidth()
        self.setMaximumWidth(10000)
        self.setMinimumWidth(1000)
        # print(self.width())

    def focusOutEvent(self, e: QtGui.QFocusEvent):
        self.setMinimumWidth(self.old_min_width)

    # #########  QComboBox 部分事件重载 结束


class StatusCell(QtWidgets.QLineEdit):
    """状态栏的单元格"""

    def __init__(self, name, label=None, data_event_engine=None, event_type=None, data_member=None, visible=True):
        """

        :param name: 控件名称
        :param label: 显示固定部分
        :param data_event_engine: 引起显示可变部分事件引擎，如果为None,仅显示固定部分
        :event_type: 事件类型
        """
        super(StatusCell, self).__init__()
        self.name = name
        self.setObjectName(name)
        self.label = label if label else ''
        self.data_event_engine = data_event_engine
        self.event_type = event_type
        self.data_member = data_member
        self.register()
        self.setVisible(visible)
        if self.label:
            self.setText(self.label)

        self.setEnabled(False)

    def register(self):
        if self.data_event_engine and self.event_type:
            self.data_event_engine.register(self.event_type, self.update_event)

    def update_event(self, event):
        data = event.data
        # print(data)
        if not (isinstance(data, dict) or isinstance(data, (bool, int, float, str))):
            data = data.__dict__
        if isinstance(data, bool):
            if data:
                data = '1'
            else:
                data = '0'
        if self.data_member:
            ds = str(data[self.data_member])
            if isinstance(data[self.data_member], (float)):
                ds = f'{data[self.data_member]:.2f}'
            self.setText(''.join([self.label, ds]))
        else:
            self.setText(''.join([self.label, str(data)]))


class CheckList(QtWidgets.QListWidget):

    def __int__(self, parent):
        super().__int__(parent)
        self.checked_values: list = []

    def activated(self):
        self.checked_values = self.selectedItems()


# endregion of boxi added


# region of cell define


class BaseCell(QtWidgets.QTableWidgetItem):
    """
    General cell used in tablewidgets.
    """

    def __init__(self, content: Any, data: Any) -> None:
        """"""
        super().__init__()
        self.setTextAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
        self.set_content(content, data)

    def set_content(self, content: Any, data: Any) -> None:
        """
        Set text content.
        """
        self.setText(str(content))
        self._data = data

    def get_data(self) -> Any:
        """
        Get data object.
        """
        return self._data


class EnumCell(BaseCell):
    """
    Cell used for showing enum data.
    """

    def __init__(self, content: str, data: Any) -> None:
        """"""
        super().__init__(content, data)

    def set_content(self, content: Any, data: Any) -> None:
        """
        Set text using enum.constant.value.
        """
        if content:
            super().set_content(content.value, data)


class DirectionCell(EnumCell):
    """
    Cell used for showing direction data.
    """

    def __init__(self, content: str, data: Any) -> None:
        """"""
        super().__init__(content, data)

    def set_content(self, content: Any, data: Any) -> None:
        """
        Cell color is set according to direction.
        """
        super().set_content(content, data)

        if content is Direction.SHORT:
            self.setForeground(COLOR_SHORT)
        else:
            self.setForeground(COLOR_LONG)


class BidCell(BaseCell):
    """
    Cell used for showing bid price and volume.
    """

    def __init__(self, content: Any, data: Any) -> None:
        """"""
        super().__init__(content, data)

        self.setForeground(COLOR_BID)


class AskCell(BaseCell):
    """
    Cell used for showing ask price and volume.
    """

    def __init__(self, content: Any, data: Any) -> None:
        """"""
        super().__init__(content, data)

        self.setForeground(COLOR_ASK)


class PnlCell(BaseCell):
    """
    Cell used for showing pnl data.
    """

    def __init__(self, content: Any, data: Any) -> None:
        """"""
        super().__init__(content, data)

    def set_content(self, content: Any, data: Any) -> None:
        """
        Cell color is set based on whether pnl is
        positive or negative.
        """
        super().set_content(content, data)

        if str(content).startswith("-"):
            self.setForeground(COLOR_SHORT)
        else:
            self.setForeground(COLOR_LONG)


class TimeCell(BaseCell):
    """
    Cell used for showing time string from datetime object.
    """

    local_tz = ZoneInfo(get_localzone_name())

    def __init__(self, content: Any, data: Any) -> None:
        """"""
        super().__init__(content, data)

    def set_content(self, content: Any, data: Any) -> None:
        """"""
        if content is None:
            return

        content: datetime = content.astimezone(self.local_tz)
        timestamp: str = content.strftime("%H:%M:%S")

        millisecond: int = int(content.microsecond / 1000)
        if millisecond:
            timestamp = f"{timestamp}.{millisecond}"
        else:
            timestamp = f"{timestamp}.000"

        self.setText(timestamp)
        self._data = data


class DateTimeCell(BaseCell):
    """
    Cell used for showing time string from datetime object.
    """

    def __init__(self, content: Any, data: Any):
        """"""
        super().__init__(content, data)

    def set_content(self, content: Any, data: Any):
        """
        DateTime format is YYYY-mm-dd 12:12:12.5
        """
        if content is None:
            return

        timestamp = content.strftime("%Y-%m-%d %H:%M:%S")

        millisecond = int(content.microsecond / 1000)
        if millisecond:
            timestamp = f"{timestamp}.{millisecond}"

        self.setText(timestamp)
        self._data = data


class MsgCell(BaseCell):
    """
    Cell used for showing msg data.
    """

    def __init__(self, content: str, data: Any) -> None:
        """"""
        super().__init__(content, data)
        self.setTextAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)


# endregion of cell define


class BaseMonitor(QtWidgets.QTableWidget):
    """
    Monitor data update.
    """

    event_type: str = ""
    data_key: str = ""
    sorting: bool = False
    headers: dict = {}

    signal: QtCore.Signal = QtCore.Signal(Event)

    def __init__(self, main_engine: MainEngine, event_engine: EventEngine) -> None:
        """"""
        super().__init__()

        self.main_engine: MainEngine = main_engine
        self.event_engine: EventEngine = event_engine
        self.cells: Dict[str, dict] = {}

        self.init_ui()
        self.load_setting()
        self.register_event()

    def init_ui(self) -> None:
        """"""
        self.init_table()
        self.init_menu()

    def init_table(self) -> None:
        """
        Initialize table.
        """
        self.setColumnCount(len(self.headers))

        labels: list = [d["display"] for d in self.headers.values()]
        self.setHorizontalHeaderLabels(labels)

        self.verticalHeader().setVisible(False)
        self.setEditTriggers(QtWidgets.QAbstractItemView.EditTrigger.NoEditTriggers)
        self.setAlternatingRowColors(True)
        self.setSortingEnabled(self.sorting)

    def init_menu(self) -> None:
        """
        Create right click menu.
        """
        self.menu: QtWidgets.QMenu = QtWidgets.QMenu(self)

        resize_action: QtGui.QAction = QtWidgets.QAction("调整列宽", self)
        resize_action.triggered.connect(self.resize_columns)
        self.menu.addAction(resize_action)

        save_action: QtGui.QAction = QtWidgets.QAction("保存数据", self)
        save_action.triggered.connect(self.save_csv)
        self.menu.addAction(save_action)

        display_action: QtGui.QAction = QtWidgets.QAction("选择显示字段", self)
        display_action.triggered.connect(self.select_item)
        self.menu.addAction(display_action)

    def select_item(self):
        if self.headers:
            lw = QtWidgets.QListWidget()
            for k, v in self.headers.items():
                itm = QtWidgets.QListWidgetItem(v['display'])
                lw.addItem(itm)


    def register_event(self) -> None:
        """
        Register event handler into event engine.
        """
        if self.event_type:
            self.signal.connect(self.process_event)
            self.event_engine.register(self.event_type, self.signal.emit)

    def process_event(self, event: Event) -> None:
        """
        Process new data from event and update into table.
        """
        # Disable sorting to prevent unwanted error.
        if self.sorting:
            self.setSortingEnabled(False)

        # Update data into table.
        data = event.data

        if not self.data_key:
            self.insert_new_row(data)
        else:
            key: str = data.__getattribute__(self.data_key)

            if key in self.cells:
                self.update_old_row(data)
            else:
                self.insert_new_row(data)

        # Enable sorting
        if self.sorting:
            self.setSortingEnabled(True)

    def insert_new_row(self, data: Any) -> None:
        """
        Insert a new row at the top of table.
        """
        self.insertRow(0)

        row_cells: dict = {}
        for column, header in enumerate(self.headers.keys()):
            setting: dict = self.headers[header]

            content = data.__getattribute__(header)
            cell: QtWidgets.QTableWidgetItem = setting["cell"](content, data)
            self.setItem(0, column, cell)

            if setting["update"]:
                row_cells[header] = cell

        if self.data_key:
            key: str = data.__getattribute__(self.data_key)
            self.cells[key] = row_cells

    def update_old_row(self, data: Any) -> None:
        """
        Update an old row in table.
        """
        key: str = data.__getattribute__(self.data_key)
        row_cells = self.cells[key]

        for header, cell in row_cells.items():
            content = data.__getattribute__(header)
            cell.set_content(content, data)

    def resize_columns(self) -> None:
        """
        Resize all columns according to contents.
        """
        self.horizontalHeader().resizeSections(QtWidgets.QHeaderView.ResizeToContents)

    def save_csv(self) -> None:
        """
        Save table data into a csv file
        """
        path, _ = QtWidgets.QFileDialog.getSaveFileName(
            self, "保存数据", "", "CSV(*.csv)")

        if not path:
            return

        with open(path, "w") as f:
            writer = csv.writer(f, lineterminator="\n")

            headers: list = [d["display"] for d in self.headers.values()]
            writer.writerow(headers)

            for row in range(self.rowCount()):
                if self.isRowHidden(row):
                    continue

                row_data: list = []
                for column in range(self.columnCount()):
                    item: QtWidgets.QTableWidgetItem = self.item(row, column)
                    if item:
                        row_data.append(str(item.text()))
                    else:
                        row_data.append("")
                writer.writerow(row_data)

    def contextMenuEvent(self, event: QtGui.QContextMenuEvent) -> None:
        """
        Show menu with right click.
        """
        self.menu.popup(QtGui.QCursor.pos())

    def save_setting(self) -> None:
        """"""
        settings: QtCore.QSettings = QtCore.QSettings(self.__class__.__name__, "custom")
        settings.setValue("column_state", self.horizontalHeader().saveState())

    def load_setting(self) -> None:
        """"""
        settings: QtCore.QSettings = QtCore.QSettings(self.__class__.__name__, "custom")
        column_state = settings.value("column_state")

        if isinstance(column_state, QtCore.QByteArray):
            self.horizontalHeader().restoreState(column_state)
            self.horizontalHeader().setSortIndicator(-1, QtCore.Qt.AscendingOrder)


class TickMonitor(BaseMonitor):
    """
    Monitor for tick data.
    """

    event_type = EVENT_TICK
    data_key = "vt_symbol"
    sorting = True

    headers = {
        "symbol": {"display": "代码", "cell": BaseCell, "update": False},
        "exchange": {"display": "交易所", "cell": EnumCell, "update": False},
        "name": {"display": "名称", "cell": BaseCell, "update": True},
        "last_price": {"display": "最新价", "cell": BaseCell, "update": True},
        "volume": {"display": "成交量", "cell": BaseCell, "update": True},
        "open": {"display": "开盘价", "cell": BaseCell, "update": True},
        "high": {"display": "最高价", "cell": BaseCell, "update": True},
        "low": {"display": "最低价", "cell": BaseCell, "update": True},
        "bid_price_1": {"display": "买1价", "cell": BidCell, "update": True},
        "bid_volume_1": {"display": "买1量", "cell": BidCell, "update": True},
        "ask_price_1": {"display": "卖1价", "cell": AskCell, "update": True},
        "ask_volume_1": {"display": "卖1量", "cell": AskCell, "update": True},
        "datetime": {"display": "时间", "cell": TimeCell, "update": True},
        "gateway_name": {"display": "接口", "cell": BaseCell, "update": False},
    }

    def __init__(self, me, ee, exchange_id=''):
        """
        :params:exchange_id: 交易所代码，自选，交易，见 bq.constant.future_exchange_dict
        """
        super().__init__(me, ee)
        self.exchange_id = exchange_id
        # print(exchange_id)
        if exchange_id:
            self.process_event = self._process_event

    def _process_event(self, event):
        data = event.data
        if data.exchange.__containt__(self.exchange_id):
            super().process_event(event)


class LogMonitor(BaseMonitor):
    """
    Monitor for log data.
    """

    event_type = EVENT_LOG
    data_key = ""
    sorting = False

    headers = {
        "time": {"display": "时间", "cell": DateTimeCell, "update": False},
        "msg": {"display": "信息", "cell": MsgCell, "update": False},
        "gateway_name": {"display": "接口", "cell": BaseCell, "update": False},
    }


class TradeMonitor(BaseMonitor):
    """
    Monitor for trade data.
    """

    event_type = EVENT_TRADE
    data_key = "vt_tradeid"
    sorting = True

    headers = {
        "symbol": {"display": "代码", "cell": BaseCell, "update": False},
        "exchange": {"display": "交易所", "cell": EnumCell, "update": False},
        "direction": {"display": "方向", "cell": DirectionCell, "update": False},
        "offset": {"display": "开平", "cell": EnumCell, "update": False},
        "price": {"display": "价格", "cell": BaseCell, "update": False},
        "volume": {"display": "数量", "cell": BaseCell, "update": False},
        "tradeid": {"display": "成交号 ", "cell": BaseCell, "update": False},
        "orderid": {"display": "委托号", "cell": BaseCell, "update": False},
        "time": {"display": "时间", "cell": BaseCell, "update": False},
        "gateway_name": {"display": "接口", "cell": BaseCell, "update": False},
    }


class ClosedPositionMonitor(TradeMonitor):
    """平仓"""

    def process_event(self, event):
        """
        Hides the row if order is not close_type.
        """
        trade = event.data
        if trade.offset != Offset.OPEN:
            super(ClosedPositionMonitor, self).process_event(event)
        # vt_id = trade.vt_tradeid
        # row_cells = self.cells[vt_id]
        #
        # if not row_cells:
        #     # for k, v in self.cells.items():
        #     #     print('ClosedPositionMonitor', k, v)
        #     return
        #
        # row = self.row(row_cells["volume"])
        # # print(trade.offset, trade.offset.value, Offset.OPEN.value)
        #
        # if trade.offset != Offset.OPEN:  # '平':
        #     self.showRow(row)
        # else:
        #     self.hideRow(row)


class OrderMonitor(BaseMonitor):
    """
    Monitor for order data.
    """

    event_type = EVENT_ORDER
    data_key = "vt_orderid"
    sorting = True

    headers = {
        "symbol": {"display": "代码", "cell": BaseCell, "update": False},
        "exchange": {"display": "交易所", "cell": EnumCell, "update": False},
        "order_type": {"display": "类型", "cell": EnumCell, "update": False},
        "direction": {"display": "方向", "cell": DirectionCell, "update": False},
        "offset": {"display": "开平", "cell": EnumCell, "update": False},
        "price": {"display": "价格", "cell": BaseCell, "update": False},
        "volume": {"display": "总数量", "cell": BaseCell, "update": True},
        "traded": {"display": "已成交", "cell": BaseCell, "update": True},
        "status": {"display": "状态", "cell": EnumCell, "update": True},
        "time": {"display": "时间", "cell": BaseCell, "update": True},
        "gateway_name": {"display": "接口", "cell": BaseCell, "update": False},
        "orderid": {"display": "委托号", "cell": BaseCell, "update": False},
    }

    def init_ui(self):
        """
        Connect signal.
        """
        super(OrderMonitor, self).init_ui()

        self.setToolTip("双击单元格撤单")
        self.itemDoubleClicked.connect(self.cancel_order)

    def cancel_order(self, cell):
        """
        Cancel order if cell double clicked.
        """
        order = cell.get_data()
        req = order.create_cancel_request()
        self.main_engine.cancel_order(req, order.gateway_name)


class PositionMonitor(BaseMonitor):
    """
    Monitor for position data.
    """

    event_type = EVENT_POSITION
    data_key = "vt_positionid"
    sorting = True

    headers = {
        "symbol": {"display": "代码", "cell": BaseCell, "update": False},
        "exchange": {"display": "交易所", "cell": EnumCell, "update": False},
        "direction": {"display": "方向", "cell": DirectionCell, "update": False},
        "volume": {"display": "数量", "cell": BaseCell, "update": True},
        "yd_volume": {"display": "昨仓", "cell": BaseCell, "update": True},
        "frozen": {"display": "冻结", "cell": BaseCell, "update": True},
        "price": {"display": "均价", "cell": BaseCell, "update": True},
        "pnl": {"display": "盈亏", "cell": PnlCell, "update": True},
        "gateway_name": {"display": "接口", "cell": BaseCell, "update": False},
    }


class PositionDetailMonitor(BaseMonitor):
    """
    Monitor for position detail data.
    """

    event_type = EVENT_POSITION_DETAIL
    data_key = "vt_positionid"
    sorting = True

    headers = {
        "symbol": {"display": "代码", "cell": BaseCell, "update": False},
        "direction": {"display": "方向", "cell": DirectionCell, "update": False},
        "volume": {"display": "数量", "cell": BaseCell, "update": True},
        "price": {"display": "开仓价", "cell": BaseCell, "update": True},
        "position_profit": {"display": "持仓收益", "cell": BaseCell, "update": True},
        "trade_datetime": {"display": "开仓时间", "cell": BaseCell, "update": True},
        "exchange": {"display": "交易所", "cell": EnumCell, "update": False},
        # "pnl": {"display": "盈亏", "cell": PnlCell, "update": True},
        "gateway_name": {"display": "接口", "cell": BaseCell, "update": False},
    }


class AccountMonitor(BaseMonitor):
    """
    Monitor for account data.
    """

    event_type = EVENT_ACCOUNT
    data_key = "vt_accountid"
    sorting = True

    headers = {
        "accountid": {"display": "账号", "cell": BaseCell, "update": False},
        "balance": {"display": "资产", "cell": BaseCell, "update": True},
        "frozen": {"display": "冻结", "cell": BaseCell, "update": True},
        "available": {"display": "可用", "cell": BaseCell, "update": True},
        "gateway_name": {"display": "接口", "cell": BaseCell, "update": False},
    }


class ConnectDialog(QtWidgets.QDialog):
    """
    Start connection of a certain gateway.
    """

    def __init__(self, main_engine: MainEngine, gateway_name: str):
        """"""
        super(ConnectDialog, self).__init__()

        self.main_engine = main_engine
        self.gateway_name = gateway_name
        self.filename = f"connect_{gateway_name.lower()}.json"

        self.widgets = {}

        self.init_ui()

    def init_ui(self):
        """"""
        self.setWindowTitle(f"连接{self.gateway_name}")

        # Default setting provides field name, field data type and field default value.
        default_setting = self.main_engine.get_default_setting(
            self.gateway_name)

        # Saved setting provides field data used last time.
        loaded_setting = load_json(self.filename)

        # Initialize line edits and form layout based on setting.
        form = QtWidgets.QFormLayout()

        for field_name, field_value in default_setting.items():
            field_type = type(field_value)

            if field_type == list:
                widget = QtWidgets.QComboBox()
                widget.addItems(field_value)

                if field_name in loaded_setting:
                    saved_value = loaded_setting[field_name]
                    ix = widget.findText(saved_value)
                    widget.setCurrentIndex(ix)
            else:
                widget = QtWidgets.QLineEdit(str(field_value))

                if field_name in loaded_setting:
                    saved_value = loaded_setting[field_name]
                    widget.setText(str(saved_value))

            form.addRow(f"{field_name} <{field_type.__name__}>", widget)
            self.widgets[field_name] = (widget, field_type)

        button = QtWidgets.QPushButton("连接")
        button.clicked.connect(self.connect)
        form.addRow(button)

        self.setLayout(form)

    def connect(self):
        """
        Get setting value from line edits and connect the gateway.
        """
        setting = {}
        for field_name, tp in self.widgets.items():
            widget, field_type = tp
            if field_type == list:
                field_value = str(widget.currentText())
            else:
                field_value = field_type(widget.text())
            setting[field_name] = field_value

        save_json(self.filename, setting)

        threading.Thread(target=self.main_engine.connect(setting, self.gateway_name)).start()

        self.accept()


class TradingWidget(QtWidgets.QWidget):
    """
    General manual trading widget.
    """

    signal_tick = QtCore.pyqtSignal(Event)

    def __init__(self, main_engine: MainEngine, event_engine: EventEngine):
        """"""
        super(TradingWidget, self).__init__()

        self.main_engine = main_engine
        self.event_engine = event_engine

        self.vt_symbol = ""
        self.symbol = ""
        self.exchange = ""
        self.price_tick = 1
        self.volume = 1
        self.price = 0

        self.init_ui()
        self.register_event()

    def init_ui(self):
        """"""
        self.setFixedWidth(300)

        # Trading function area
        exchanges = self.main_engine.get_all_exchanges()
        self.exchange_combo = QtWidgets.QComboBox()
        self.exchange_combo.addItems([exchange.value for exchange in exchanges])

        self.symbol_line = QtWidgets.QLineEdit()
        self.symbol_line.returnPressed.connect(self.set_vt_symbol)

        self.name_line = QtWidgets.QLineEdit()
        self.name_line.setReadOnly(True)

        self.direction_combo = QtWidgets.QComboBox()
        self.direction_combo.addItems(
            [Direction.LONG.value, Direction.SHORT.value])

        self.offset_combo = QtWidgets.QComboBox()
        self.offset_combo.addItems([offset.value for offset in Offset])
        self.offset_combo.setCurrentIndex(1)

        self.order_type_combo = QtWidgets.QComboBox()
        self.order_type_combo.addItems(
            [order_type.value for order_type in OrderType])

        double_validator = QtGui.QDoubleValidator()
        double_validator.setBottom(1)

        self.pb_price_type = QtWidgets.QPushButton()
        self.pb_price_type.setText('市价')
        self.pb_price_type.clicked.connect(self.pb_price_type_clicked)

        self.price_line = QtWidgets.QLineEdit()
        self.price_line.setValidator(double_validator)
        self.price_spin = QtWidgets.QDoubleSpinBox()
        self.price_spin.setMaximum(9999)
        self.price_spin.setProperty("value", 1)
        self.price_spin.setMinimum(1)
        self.price_spin.valueChanged.connect(self.price_spin_changeEvent)

        self.volume_line = QtWidgets.QLineEdit()
        self.volume_line.setValidator(double_validator)

        self.volume_spin = QtWidgets.QSpinBox()
        self.volume_spin.setMaximum(9999)
        self.volume_spin.setProperty("value", 1)
        self.volume_spin.setMinimum(1)
        self.volume_spin.valueChanged.connect(self.volume_spin_changeEvent)
        self.volume_line.setText(str(self.volume_spin.value()))

        self.gateway_combo = QtWidgets.QComboBox()
        self.gateway_combo.addItems(self.main_engine.get_all_gateway_names())

        send_button = QtWidgets.QPushButton("委托")
        send_button.clicked.connect(self.send_order)

        cancel_button = QtWidgets.QPushButton("全撤")
        cancel_button.clicked.connect(self.cancel_all)

        form1 = QtWidgets.QFormLayout()
        form1.addRow("交易所", self.exchange_combo)
        form1.addRow("代码", self.symbol_line)
        form1.addRow("名称", self.name_line)
        form1.addRow("多空", self.direction_combo)
        form1.addRow("开平", self.offset_combo)
        form1.addRow("类型", self.order_type_combo)
        form1.addRow(self.pb_price_type, self.price_spin)  # "价格"
        form1.addRow("数量", self.volume_spin)
        form1.addRow("接口", self.gateway_combo)
        form1.addRow(send_button)
        form1.addRow(cancel_button)

        # Market depth display area
        bid_color = "rgb(255,174,201)"
        ask_color = "rgb(160,255,160)"

        self.bp1_label = self.create_label(bid_color)
        self.bp2_label = self.create_label(bid_color)
        self.bp3_label = self.create_label(bid_color)
        self.bp4_label = self.create_label(bid_color)
        self.bp5_label = self.create_label(bid_color)

        self.bv1_label = self.create_label(
            bid_color, alignment=QtCore.Qt.AlignmentFlag.AlignRight)
        self.bv2_label = self.create_label(
            bid_color, alignment=QtCore.Qt.AlignmentFlag.AlignRight)
        self.bv3_label = self.create_label(
            bid_color, alignment=QtCore.Qt.AlignmentFlag.AlignRight)
        self.bv4_label = self.create_label(
            bid_color, alignment=QtCore.Qt.AlignmentFlag.AlignRight)
        self.bv5_label = self.create_label(
            bid_color, alignment=QtCore.Qt.AlignmentFlag.AlignRight)

        self.ap1_label = self.create_label(ask_color)
        self.ap2_label = self.create_label(ask_color)
        self.ap3_label = self.create_label(ask_color)
        self.ap4_label = self.create_label(ask_color)
        self.ap5_label = self.create_label(ask_color)

        self.av1_label = self.create_label(
            ask_color, alignment=QtCore.Qt.AlignmentFlag.AlignRight)
        self.av2_label = self.create_label(
            ask_color, alignment=QtCore.Qt.AlignmentFlag.AlignRight)
        self.av3_label = self.create_label(
            ask_color, alignment=QtCore.Qt.AlignmentFlag.AlignRight)
        self.av4_label = self.create_label(
            ask_color, alignment=QtCore.Qt.AlignmentFlag.AlignRight)
        self.av5_label = self.create_label(
            ask_color, alignment=QtCore.Qt.AlignmentFlag.AlignRight)

        self.lp_label = self.create_label()
        self.return_label = self.create_label(alignment=QtCore.Qt.AlignmentFlag.AlignRight)

        form2 = QtWidgets.QFormLayout()
        form2.addRow(self.ap5_label, self.av5_label)
        form2.addRow(self.ap4_label, self.av4_label)
        form2.addRow(self.ap3_label, self.av3_label)
        form2.addRow(self.ap2_label, self.av2_label)
        form2.addRow(self.ap1_label, self.av1_label)
        form2.addRow(self.lp_label, self.return_label)
        form2.addRow(self.bp1_label, self.bv1_label)
        form2.addRow(self.bp2_label, self.bv2_label)
        form2.addRow(self.bp3_label, self.bv3_label)
        form2.addRow(self.bp4_label, self.bv4_label)
        form2.addRow(self.bp5_label, self.bv5_label)

        # Overall layout
        vbox = QtWidgets.QHBoxLayout()
        vbox.addLayout(form1)
        vbox.addLayout(form2)
        self.setLayout(vbox)

    def pb_price_type_clicked(self):
        if self.pb_price_type.text() == '市价':
            self.pb_price_type.setText('限价')
        else:
            self.pb_price_type.setText('市价')

    def on_instrument_id_changed(self, iid: str):
        if self.symbol != iid:
            self.clear_label_text()
            self.symbol = iid
            self.exchange = self.main_engine.get_contract_by_symbol(iid).get('exchange', '')
            self.vt_symbol = '.'.join([self.symbol, self.exchange.value]) if isinstance(self.exchange,
                                                                                        Exchange) else '.'.join(
                [self.symbol, self.exchange])
            self.price_tick = self.main_engine.get_price_tick(self.vt_symbol)
            self.price_spin.setSingleStep(float(self.price_tick))

        if self.exchange:
            self.exchange_combo.setCurrentText(self.exchange.value)
            self.symbol_line.setText(iid)

    def volume_spin_changeEvent(self, qEvent):
        v = self.volume_spin.text()
        self.volume_line.setText(v)

    def price_spin_changeEvent(self, qEvent):
        v = self.price_spin.text()
        self.price_line.setText(str(v))

    def create_label(self, color: str = "", alignment=QtCore.Qt.AlignmentFlag.AlignLeft):
        """
        Create label with certain font color.
        """
        label = QtWidgets.QLabel()
        if color:
            label.setStyleSheet(f"color:{color}")
        label.setAlignment(alignment)
        return label

    def register_event(self):
        """"""
        self.signal_tick.connect(self.process_tick_event)
        self.event_engine.register(EVENT_TICK, self.signal_tick.emit)

    def process_tick_event(self, event: Event):
        """"""
        tick = event.data
        # print('process_tick_event of TradingWidget:',tick)
        if tick.symbol != self.vt_symbol and tick.symbol != self.symbol:
            return
        if self.pb_price_type.text() == '市价':
            self.price_line.setText(str(tick.last_price))
            self.price_spin.setValue(float(tick.last_price))

        self.lp_label.setText(str(tick.last_price))
        self.bp1_label.setText(str(tick.bid_price_1))
        self.bv1_label.setText(str(tick.bid_volume_1))
        self.ap1_label.setText(str(tick.ask_price_1))
        self.av1_label.setText(str(tick.ask_volume_1))

        if tick.pre_close:
            r = (tick.last_price / tick.pre_close - 1) * 100
            self.return_label.setText(f"{r:.2f}%")

        if tick.bid_price_2:
            self.bp2_label.setText(str(tick.bid_price_2))
            self.bv2_label.setText(str(tick.bid_volume_2))
            self.ap2_label.setText(str(tick.ask_price_2))
            self.av2_label.setText(str(tick.ask_volume_2))

            self.bp3_label.setText(str(tick.bid_price_3))
            self.bv3_label.setText(str(tick.bid_volume_3))
            self.ap3_label.setText(str(tick.ask_price_3))
            self.av3_label.setText(str(tick.ask_volume_3))

            self.bp4_label.setText(str(tick.bid_price_4))
            self.bv4_label.setText(str(tick.bid_volume_4))
            self.ap4_label.setText(str(tick.ask_price_4))
            self.av4_label.setText(str(tick.ask_volume_4))

            self.bp5_label.setText(str(tick.bid_price_5))
            self.bv5_label.setText(str(tick.bid_volume_5))
            self.ap5_label.setText(str(tick.ask_price_5))
            self.av5_label.setText(str(tick.ask_volume_5))

    def set_vt_symbol(self):
        """
        Set the tick depth data to monitor by vt_symbol.
        """
        symbol = str(self.symbol_line.text())
        if not symbol:
            return
        self.symbol = symbol

        # Generate vt_symbol from symbol and exchange
        exchange_value = str(self.exchange_combo.currentText())
        vt_symbol = f"{symbol}.{exchange_value}"

        if vt_symbol == self.vt_symbol:
            return
        self.vt_symbol = vt_symbol

        # Update name line widget and clear all labels
        contract = self.main_engine.get_contract(vt_symbol)
        if not contract:
            self.name_line.setText("")
            gateway_name = self.gateway_combo.currentText()
        else:
            self.name_line.setText(contract.stra_name)
            self.price_tick = contract.pricetick
            gateway_name = contract.gateway_name

            # Update gateway combo box.
            ix = self.gateway_combo.findText(gateway_name)
            self.gateway_combo.setCurrentIndex(ix)

        self.clear_label_text()

        # Subscribe tick data
        req = SubscribeRequest(
            symbol=symbol, exchange=Exchange(exchange_value)
        )

        self.main_engine.subscribe(req, gateway_name)

    def clear_label_text(self):
        """
        Clear text on all labels.
        """
        self.lp_label.setText("")
        self.return_label.setText("")

        self.bv1_label.setText("")
        self.bv2_label.setText("")
        self.bv3_label.setText("")
        self.bv4_label.setText("")
        self.bv5_label.setText("")

        self.av1_label.setText("")
        self.av2_label.setText("")
        self.av3_label.setText("")
        self.av4_label.setText("")
        self.av5_label.setText("")

        self.bp1_label.setText("")
        self.bp2_label.setText("")
        self.bp3_label.setText("")
        self.bp4_label.setText("")
        self.bp5_label.setText("")

        self.ap1_label.setText("")
        self.ap2_label.setText("")
        self.ap3_label.setText("")
        self.ap4_label.setText("")
        self.ap5_label.setText("")

    def send_order(self):
        """
        Send new order manually.
        """
        symbol = str(self.symbol_line.text())
        if not symbol:
            QtWidgets.QMessageBox.critical(self, "委托失败", "请输入合约代码")
            return

        volume_text = str(self.volume_line.text())
        if not volume_text:
            QtWidgets.QMessageBox.critical(self, "委托失败", "请输入委托数量")
            return
        volume = float(volume_text)

        price_text = str(self.price_line.text())
        if not price_text:
            price = 0
        else:
            price = float(price_text)

        req = OrderRequest(
            symbol=symbol,
            exchange=Exchange(str(self.exchange_combo.currentText())),
            direction=Direction(str(self.direction_combo.currentText())),
            type=OrderType(str(self.order_type_combo.currentText())),
            volume=volume,
            price=price,
            offset=Offset(str(self.offset_combo.currentText())),
        )

        gateway_name = str(self.gateway_combo.currentText())

        self.main_engine.send_order(req, gateway_name)

    def cancel_all(self):
        """
        Cancel all active orders.
        """
        order_list = self.main_engine.get_all_active_orders()
        for order in order_list:
            req = order.create_cancel_request()
            self.main_engine.cancel_order(req, order.gateway_name)


class ActiveOrderMonitor(OrderMonitor):
    """
    Monitor which shows active order only.
    """

    def process_event(self, event):
        """
        Hides the row if order is not active.
        """
        super(ActiveOrderMonitor, self).process_event(event)

        order = event.data
        # for k, v in self.cells.items():
        #     print('ActiveOrderMonitor:',k,v)
        row_cells = self.cells[order.vt_orderid]
        row = self.row(row_cells["volume"])

        if order.is_active():
            self.showRow(row)
        else:
            self.hideRow(row)


class PreOrderMonitor(OrderMonitor):
    """
    Monitor which shows parked order only.
    """

    def process_event(self, event):
        """
        Hides the row if order is not active.
        """
        super(PreOrderMonitor, self).process_event(event)

        order = event.data
        # for k, v in self.cells.items():
        #     print('ActiveOrderMonitor:',k,v)
        row_cells = self.cells[order.vt_orderid]
        row = self.row(row_cells["volume"])

        if order.is_active():
            self.showRow(row)
        else:
            self.hideRow(row)


class ContractManager(QtWidgets.QWidget):
    """
    Query contract data available to trade in system.
    """

    headers = {
        "vt_symbol": "本地代码",
        "symbol": "代码",
        "exchange": "交易所",
        "name": "名称",
        "product": "合约分类",
        "size": "合约乘数",
        "pricetick": "价格跳动",
        "min_volume": "最小委托量",
        "gateway_name": "交易接口",
    }

    def __init__(self, main_engine, event_engine):
        super(ContractManager, self).__init__()

        self.main_engine = main_engine
        self.event_engine = event_engine

        self.init_ui()

    def init_ui(self):
        """"""
        self.setWindowTitle("合约查询")
        self.resize(1000, 600)

        self.filter_line = QtWidgets.QLineEdit()
        self.filter_line.setPlaceholderText("输入合约代码或者交易所，留空则查询所有合约")

        self.button_show = QtWidgets.QPushButton("查询")
        self.button_show.clicked.connect(self.show_contracts)

        labels = []
        for name, display in self.headers.items():
            label = f"{display}\n{name}"
            labels.append(label)

        self.contract_table = QtWidgets.QTableWidget()
        self.contract_table.setColumnCount(len(self.headers))
        self.contract_table.setHorizontalHeaderLabels(labels)
        self.contract_table.verticalHeader().setVisible(False)
        self.contract_table.setEditTriggers(self.contract_table.NoEditTriggers)
        self.contract_table.setAlternatingRowColors(True)

        hbox = QtWidgets.QHBoxLayout()
        hbox.addWidget(self.filter_line)
        hbox.addWidget(self.button_show)

        vbox = QtWidgets.QVBoxLayout()
        vbox.addLayout(hbox)
        vbox.addWidget(self.contract_table)

        self.setLayout(vbox)

    def show_contracts(self):
        """
        Show contracts by symbol
        """
        flt = str(self.filter_line.text())
        print(flt)

        all_contracts = self.main_engine.get_all_contracts()
        if flt:
            contracts = [
                contract for contract in all_contracts if flt in contract.symbol
            ]
        else:
            contracts = all_contracts

        self.contract_table.clearContents()
        self.contract_table.setRowCount(len(contracts))

        for row, contract in enumerate(contracts):
            for column, name in enumerate(self.headers.keys()):
                value = getattr(contract, name)
                if isinstance(value, Enum):
                    cell = EnumCell(value, contract)
                else:
                    cell = BaseCell(value, contract)
                self.contract_table.setItem(row, column, cell)

        self.contract_table.resizeColumnsToContents()


class AboutDialog(QtWidgets.QDialog):
    """
    About VN Trader.
    """

    def __init__(self, main_engine: MainEngine, event_engine: EventEngine):
        """"""
        super(AboutDialog, self).__init__()

        self.main_engine = main_engine
        self.event_engine = event_engine

        self.init_ui()

    def init_ui(self):
        """"""
        self.setWindowTitle(f"关于VN Trader")

        text = """
            Developed by Traders, for Traders.
            License：MIT

            Website：www.vnpy.com
            Github：www.github.com/vnpy/vnpy

            """

        label = QtWidgets.QLabel()
        label.setText(text)
        label.setMinimumWidth(500)

        vbox = QtWidgets.QVBoxLayout()
        vbox.addWidget(label)
        self.setLayout(vbox)


class GlobalDialog(QtWidgets.QDialog):
    """
    Start connection of a certain gateway.
    """

    def __init__(self):
        """"""
        super().__init__()

        self.widgets = {}
        self.widgets_count = 0
        self.new_widgets = {}

        self.init_ui()

    def init_ui(self):
        """"""
        self.setWindowTitle("全局配置")
        self.setMinimumWidth(1000)

        settings = copy(SETTINGS)
        settings.update(load_json(SETTING_FILENAME))

        # Initialize line edits and form layout based on setting.
        form = QtWidgets.QGridLayout()
        form.setVerticalSpacing(2)
        form1 = QtWidgets.QFormLayout()
        form1.setVerticalSpacing(2)
        form2 = QtWidgets.QFormLayout()
        form2.setVerticalSpacing(2)
        ok_btn = QtWidgets.QPushButton("确定")
        ok_btn.clicked.connect(self.update_setting)
        add_btn = QtWidgets.QPushButton("新增")
        add_btn.clicked.connect(self.add_setting)
        form.addWidget(add_btn, 0, 0)
        form.addWidget(ok_btn, 0, 1)
        form.addLayout(form1, 1, 0)
        form.addLayout(form2, 1, 1)
        i = 0
        for field_name, field_value in settings.items():
            field_type = type(field_value)
            widget = QtWidgets.QLineEdit(str(field_value))
            if i % 2 == 0:
                form1.addRow(f"{field_name} <{field_type.__name__}>", widget)
            else:
                form2.addRow(f"{field_name} <{field_type.__name__}>", widget)
            i += 1
            self.widgets[field_name] = (widget, field_type)
            self.widgets_count += 1
        self.form1 = form1
        self.form2 = form2
        self.setLayout(form)

    def add_setting(self):
        if self.widgets_count % 2 == 0:
            form = self.form1
        else:
            form = self.form2
        field_name, field_value = 'field_name', 'field_value'
        value_widget = QtWidgets.QLineEdit(field_value)
        name_widget = QtWidgets.QLineEdit(field_name)
        form.addRow(name_widget, value_widget)
        self.widgets_count += 1

    def update_setting(self):
        """
        Get setting value from line edits and update global setting file.
        """
        settings = {}
        for field_name, tp in self.widgets.items():
            widget, field_type = tp
            value_text = widget.text()

            if field_type == bool:
                if value_text == "True":
                    field_value = True
                else:
                    field_value = False
            else:
                field_value = field_type(value_text)

            settings[field_name] = field_value

        QtWidgets.QMessageBox.information(
            self,
            "注意",
            "全局配置的修改需要重启VN Trader后才会生效！",
            QtWidgets.QMessageBox.Ok
        )

        save_json(SETTING_FILENAME, settings)
        self.accept()


from vnpy.ui_tools.tick_widget import TickWidget


class TickTaGraph(TickWidget):
    """
    tick 图
    """

    def __init__(self, me, ee, parent=None, data=None, fields_name=None):
        super(TickTaGraph, self).__init__(parent=parent, data=data, fields_name=fields_name)
        self.me = me
        self.ee = ee
        self.vt_symbol = ''
        self.ticks = None
        self.bars = None


from vnpy.ui.dw_quote import Ui_dw_quote
from vnpy.bq.constant import future_exchange_dict


class QuoteList(QtWidgets.QDockWidget, Ui_dw_quote):
    def __init__(self, me, ee, *args, **kwargs):
        super(QuoteList, self).__init__()
        self.setupUi(self)
        self.main_engine = me
        self.event_engine = ee
        f_tt_idx = {}
        for k, v in future_exchange_dict.items():
            idx = 0
            while idx < self.twChinaFuture.count():
                w = self.twChinaFuture.widget(idx).layout()
                txt = w.objectName()
                # print(txt)
                if txt.__contains__(k + 'Layout'):
                    self._init_talbe(w, k)
                idx += 1

    def _init_talbe(self, layout, eid):
        table = TickMonitor(self.main_engine, self.event_engine, eid)
        # print(eid,self.main_engine.exchange_symbol_map)
        if not self.main_engine.exchange_symbol_map.get(eid):
            return
        for itm in self.main_engine.exchange_symbol_map[eid]:
            table.insertRow(0)
            table.setItem(0, 0, QtWidgets.QTableWidgetItem(itm))
        layout.addWidget(table)


from vnpy.ui.w_account import Ui_DockWidgetAccount

from vnpy.ui_tools.pandas_model import PandasModel


class AccountWidget(QtWidgets.QDockWidget, Ui_DockWidgetAccount):
    """账户管理器"""
    park_order_signal = pyqtSignal()
    instrument_changed = pyqtSignal(str)

    def __init__(self, me, ee, *args):
        super(AccountWidget, self).__init__()
        self.setupUi(self)
        self.event_type = {'account': EVENT_ACCOUNT, 'position': EVENT_POSITION,
                           'position_detail': EVENT_POSITION_DETAIL, 'trade': EVENT_TRADE,
                           'order': EVENT_ORDER}
        self.setWindowTitle('账户管理')
        self.me = me
        self.ee = ee
        self.current_instrument_id = ''
        self.acc_timer = QTimer(self)

        self.register_widget_events()
        self.account = {}
        self.order = {}
        self.position = {}
        self.position_detail = {}
        self.trade = {}
        self.working_order = {}
        self.parked_order = {}  # 预埋单
        self._create_account_tables()
        # self.init_table()
        self.symbol = ''
        self.vt_symbol = ''
        self.volume_multiple = 10
        self.price_tick = 1.0
        self.exchange_id = ''
        self.exchange = ''
        self.price_digits = 0

        self.price = 0
        self.contracts = {} if not me else me.get_all_contracts()
        self.instrument_info = {v.symbol: v for v in self.contracts}

        # if self.parent:
        #     self.parent.instrument_changed.connect(self.on_instrument_changed)

        if self.ee:
            self.ee.register(EVENT_TICK, self.update_data)

        # 设置下单窗口始终处于前端
        self.setWindowFlags(QtCore.Qt.WindowFlags.WindowStaysOnTopHint)
        self._register_widget_event()

        self.order_ok = True
        self.park_order = False

    def _register_widget_event(self):
        self.pushButtonOrder.clicked.connect(self.order_clicked)
        self.pushButtonPriceType.clicked.connect(self.pbPriceType_clicked)
        self.spinBoxPrice.valueChanged.connect(self.spinBoxPrice_valueChanged)
        self.pushButtonBuy.clicked.connect(self.buy_clicked)
        self.pushButtonSell.clicked.connect(self.sell_clicked)
        self.pushButtonShort.clicked.connect(self.short_clicked)
        self.pushButtonCover.clicked.connect(self.cover_clicked)

        self.comboBoxSymbol.focusOutEvent = self.comboBoxSymbol_focusOutEvent

        self.comboBoxSymbol.setEditable(True)
        self.comboBoxSymbol.addItems([k for k in self.instrument_info.keys()])

    def comboBoxSymbol_focusOutEvent(self, e):
        iid = self.comboBoxSymbol.currentText()

        if iid in self.instrument_info.keys():
            ii = self.instrument_info.get(iid, {})
            # print(iid)
            if ii:
                self.symbol = iid
                self.price_tick = ii.get('pricetick', ii.get('PriceTick', self.price_tick))
                self.exchange_id = ii.get('ExchangeID', self.exchange_id)
                self.exchange = ii.get('exchange')
                self.spinBoxPrice.setSingleStep(self.price_tick)
                # print(self.price_tick)
                # if self.parent:
                #     self.spinBoxPrice.setValue(self.parent.last_price)

    @QtCore.pyqtSlot(str)
    def on_instrument_changed(self, iid: str):
        # self.lineEditSymbol.setText(iid)
        # print(iid)
        if iid.__contains__('.'):  # 此时的IID是一 vt_symbol: symbol.Exchange
            sbl, xcg = iid.split('.')
            if self.symbol != sbl:
                self.symbol = sbl
                self.exchange = xcg
                self.vt_symbol = iid
                self.price_tick = self.me.get_price_tick(self.vt_symbol)
                self.price_digits = get_digits(self.price_tick)
                self.comboBoxSymbol.setEditable(True)
                self.comboBoxSymbol.setEditText(sbl)
                self.spinBoxPrice.setSingleStep(self.price_tick)
            return

        if self.symbol != iid:
            self.symbol = iid
            self.exchange = self.me.get_contract_by_symbol(iid).__dict__.get('exchange', '')
            self.vt_symbol = '.'.join([self.symbol, self.exchange.value])
            self.price_tick = self.me.get_price_tick(self.vt_symbol)
            self.comboBoxSymbol.setEditable(True)
            self.comboBoxSymbol.setEditText(iid)
            self.spinBoxPrice.setSingleStep(self.price_tick)

    def buy_clicked(self):
        """开多"""
        symbol = self.comboBoxSymbol.currentText()
        price = self.spinBoxPrice.value()
        vol = self.spinBoxVol.value()
        data = OrderRequest(
            price=price,
            volume=vol,
            exchange=self.exchange,
            symbol=symbol,
            order_type=OrderType.LIMIT,
            direction=Direction.LONG,
            offset=Offset.OPEN)
        # msg = f'交易标的：{data.symbol}，价格：{data.price}，手数：{data.volume}，开多'
        # if not (QMessageBox.information(self, '交易信息', msg, QMessageBox.Ok | QMessageBox.Cancel) == QMessageBox.Ok):
        #     return
        self._send_order(data)

    def short_clicked(self):
        """开空"""
        symbol = self.comboBoxSymbol.currentText()
        price = self.spinBoxPrice.value()
        vol = self.spinBoxVol.value()
        data = OrderRequest(
            price=price,
            volume=vol,
            symbol=symbol,
            exchange=self.exchange,
            order_type=OrderType.LIMIT,
            direction=Direction.SHORT,
            offset=Offset.OPEN)
        # msg = f'交易标的：{data.symbol}，价格：{data.price}，手数：{data.volume}，开空'
        # if not (QMessageBox.information(self, '交易信息', msg, QMessageBox.Ok | QMessageBox.Cancel) == QMessageBox.Ok):
        #     return
        self._send_order(data)

    def sell_clicked(self):
        """平多"""
        symbol = self.comboBoxSymbol.currentText()
        price = self.spinBoxPrice.value()
        vol = self.spinBoxVol.value()
        data = OrderRequest(
            price=price,
            volume=vol,
            symbol=symbol,
            exchange=self.exchange,
            order_type=OrderType.LIMIT,
            direction=Direction.SHORT,
            offset=Offset.CLOSE)
        # msg = f'交易标的：{data.symbol}，价格：{data.price}，手数：{data.volume}，平多'
        # if not (QMessageBox.information(self, '交易信息', msg, QMessageBox.Ok | QMessageBox.Cancel) == QMessageBox.Ok):
        #     return
        self._send_order(data)

    def cover_clicked(self):
        """平空"""
        symbol = self.comboBoxSymbol.currentText()
        price = self.spinBoxPrice.value()
        vol = self.spinBoxVol.value()
        data = OrderRequest(
            price=price,
            volume=vol,
            symbol=symbol,
            exchange=self.exchange,
            order_type=OrderType.LIMIT,
            direction=Direction.LONG,
            offset=Offset.CLOSE)
        # msg = f'交易标的：{data.symbol}，价格：{data.price}，手数：{data.volume}，平空'
        # if not (QMessageBox.information(self, '交易信息', msg, QMessageBox.Ok | QMessageBox.Cancel) == QMessageBox.Ok):
        #     return
        self._send_order(data)

    def spinBoxPrice_valueChanged(self, e):
        # print(e)
        value = self.spinBoxPrice.value()
        # print(value, str(value))
        self.pushButtonBuy.setText(str(value) + '\n' + '开多')
        self.pushButtonShort.setText(str(value) + '\n' + '开空')
        self.pushButtonSell.setText(str(value) + '\n' + '平多')
        self.pushButtonCover.setText(str(value) + '\n' + '平空')

    def update_data(self, tick_event):
        """
        :param tick:
        :return:
        """
        tick = tick_event.data
        if tick.symbol not in [self.symbol, self.vt_symbol]:
            return
        sap1 = f'{tick.ask_price_1:.{self.price_digits}f}' if self.price_digits > 0 else f'{tick.ask_price_1}'
        sbp1 = f'{tick.bid_price_1:.{self.price_digits}f}' if self.price_digits > 0 else f'{tick.bid_price_1}'
        slup = f'{tick.limit_up:.{self.price_digits}f}' if self.price_digits > 0 else f'{tick.limit_up}'
        sldown = f'{tick.limit_down:.{self.price_digits}f}' if self.price_digits > 0 else f'{tick.limit_down}'

        if self.pushButtonPriceType.text() == '市价':
            self.spinBoxPrice.setValue(
                tick.last_price)  # setSpecialValueText(f'{tick.last_price:.{self.price_digits}f}')
            self.pushButtonBuy.setText(f'{sap1}\n开多')
            self.pushButtonShort.setText(f'{sbp1}\n开空')
            self.pushButtonSell.setText(f'{sap1}\n平多')
            self.pushButtonCover.setText(f'{sbp1}\n平空')

        self.labelSell.setText(f'卖:{sap1}/{tick.ask_volume_1}')
        self.labelBuy.setText(f'买:{sbp1}/{tick.bid_volume_1}')
        self.labelUpper.setText(f'涨停:{slup} 最高：{tick.high_price:.{self.price_digits}f}')
        self.labelLower.setText(f'跌停:{sldown} 最低：{tick.low_price:.{self.price_digits}f}')
        self.labelUpDown.setText(f'涨跌：{tick.last_price - tick.pre_close:.{self.price_digits}f} ')

    def order_clicked(self):
        """下单"""
        # if not self.symbol:
        #     return
        symbol = self.comboBoxSymbol.currentText()
        # if self.quote_event:
        #     self.quote_event.unregister(EVENT_TICK, self.update_data)

        price = self.spinBoxPrice.value()
        vol = self.spinBoxVol.value()
        # if self.radioButtonSpeculation.isChecked():
        #     HedgeFlag = defineDict['THOST_FTDC_HF_Speculation']
        if self.radioButtonBuy.isChecked():
            direction = Direction.LONG
        else:
            direction = Direction.SHORT
        if self.radioButtonOpen.isChecked():
            offset = Offset.OPEN
        elif self.radioButtonCloseToday.isChecked():
            offset = Offset.CLOSETODAY
        elif self.radioButtonCloseYesterday.isChecked():
            offset = Offset.CLOSEYESTERDAY
        else:
            offset = Offset.CLOSE
        order_req = OrderRequest(price=price,
                                 volume=vol,
                                 symbol=symbol,
                                 exchange=self.exchange,
                                 offset=offset,
                                 direction=direction,
                                 order_type=OrderType.LIMIT)
        self._send_order(order_req)

    def _send_order(self, data):
        self.park_order = self.cbPark.isChecked()
        if self.park_order:
            self.send_park_order_signal.emit(data)
            return

        msg = f'交易标的：{data.symbol}，价格：{data.price}，手数：{data.volume}，开平：{data.offset}，多空：{data.direction}'
        if QtWidgets.QMessageBox.information(self, '交易信息', msg,
                                             QtWidgets.QMessageBox.StandardButtons.Ok |
                                             QtWidgets.QMessageBox.StandardButtons.Cancel) == \
                QtWidgets.QMessageBox.StandardButtons.Ok:
            gateway_name = 'CTP'
            self.me.send_order(data, gateway_name)
            # if self.ee:
            #     # print(orderReq.__dict__)
            #     evnt = Event()
            #     evnt.event_type = EVENT_REQ_ORDER_INSERT
            #     evnt.dict_['data'] = data
            #     self.ee.put(evnt)
            # self.sendOrderSignal.emit(data.__dict__)

    def pbPriceType_clicked(self):
        """价格来源点击事件"""
        if self.pushButtonPriceType.text() == '指定价':
            self.pushButtonPriceType.setText('市价')
            # if self.quote_event:
            #     self.quote_event.register(EVENT_TICK, self.update_data)
        else:
            self.pushButtonPriceType.setText('指定价')
            # if self.quote_event:
            #     self.quote_event.unregister(EVENT_TICK, self.update_data)

    def _create_account_tables(self):
        """创建账户相关表格"""
        # print(self.event_type)
        self.accountTable = AccountMonitor(self.me, self.ee)
        self.accountTableLayout = QtWidgets.QVBoxLayout(self.tabAccount)
        self.accountTableLayout.setObjectName('accountTableLayout')
        self.accountTableLayout.addWidget(self.accountTable)
        # 委托-下单表格
        self.orderTable = OrderMonitor(self.me, self.ee)
        self.orderTable.itemClicked.connect(self.tw_cell_clicked)
        self.orderTableLayout = QtWidgets.QVBoxLayout(self.tabOrder)
        self.orderTableLayout.setObjectName('orderTableLayout')
        self.orderTableLayout.addWidget(self.orderTable)
        # 预埋单表格 twOrder=QtWidgets.QTabWidget()
        # self.tabPreOrder = QtWidgets.QWidget()
        # self.tabPreOrder.setObjectName("tabPreOrder")
        # self.twOrder.addTab(self.tabPreOrder, "预埋委托单")
        self.porderTable = PreOrderMonitor(self.me, self.ee)
        self.porderTableLayout = QtWidgets.QVBoxLayout(self.tabPreOrder)
        self.porderTableLayout.setObjectName('porderTableLayout')
        self.porderTableLayout.addWidget(self.porderTable)

        # 成交表格
        self.tradeTable = TradeMonitor(self.me, self.ee)
        self.tradeTableLayout = QtWidgets.QVBoxLayout(self.tabTraded)
        self.tradeTableLayout.setObjectName('tradeTableLayout')
        self.tradeTableLayout.addWidget(self.tradeTable)
        # self.tradeTable.set_account_event_type(self.event_type['trade'])
        # 未成交委托表格
        self.untradeTable = ActiveOrderMonitor(self.me, self.ee)
        self.untradeTable.itemClicked.connect(self.tw_cell_clicked)
        self.untradeTableLayout = QtWidgets.QVBoxLayout(self.tabUntrade)
        self.untradeTableLayout.setObjectName('untradeTableLayout')
        self.untradeTableLayout.addWidget(self.untradeTable)
        # 持仓表格
        self.positionTable = PositionMonitor(self.me, self.ee)
        self.positionTable.itemClicked.connect(self.tw_cell_clicked)
        self.positionTableLayout = QtWidgets.QVBoxLayout(self.tabAccountPosition)
        self.positionTableLayout.setObjectName('positionTableLayout')
        self.positionTableLayout.addWidget(self.positionTable)
        # self.positionTable.set_account_event_type(self.event_type['position'])

        # 持仓明细表格
        self.positionDetailTable = PositionDetailMonitor(self.me, self.ee)
        self.positionDetailTable.itemClicked.connect(self.tw_cell_clicked)
        self.positionDetailTableLayout = QtWidgets.QVBoxLayout(self.tabPostionDetail)
        self.positionDetailTableLayout.setObjectName('positionDetailTableLayout')
        self.positionDetailTableLayout.addWidget(self.positionDetailTable)
        # # self.positionDetailTable.set_account_event_type(self.event_type['position_detail'])
        #
        # 平仓明细表格
        self.closedPositionTable = ClosedPositionMonitor(self.me, self.ee)
        self.closedPositionTableLayout = QtWidgets.QVBoxLayout(self.tabClosedPosition)
        self.closedPositionTableLayout.setObjectName('closedPositionTableLayout')
        self.closedPositionTableLayout.addWidget(self.closedPositionTable)

        self.start_acc_tiemr()

    def init_table(self):
        self.tvTrade = TradeMonitor(self.me, self.ee)
        self.tvAccount = AccountMonitor(self.me, self.ee)
        self.tvAllOrder = OrderMonitor(self.me, self.ee)
        self.tvWorking = ActiveOrderMonitor(self.me, self.ee)
        self.tvPosition = PositionMonitor(self.me, self.ee)

    def start_acc_tiemr(self):
        # 以下及时更新相关表格 ，去掉过期数据
        # print('start acc_timer....')
        self.acc_timer.timeout.connect(self._on_acc_timer)
        self.acc_timer.start(1000 * 60 * 60 * 12)

    def _on_acc_timer(self):
        print('清除过期数据。。。')
        self.orderTable.clearContents()
        self.orderTable.cells = {}
        self.untradeTable.clearContents()
        self.untradeTable.cells = {}
        self.positionTable.clearContents()
        self.positionTable.cells = {}
        self.positionDetailTable.clearContents()
        self.positionDetailTable.cells = {}
        self.closedPositionTable.clearContents()
        self.closedPositionTable.cells = {}
        self.tradeTable.clearContents()
        self.tradeTable.cells = {}

    def register_widget_events(self):
        """注册控件事件"""
        # self.pbQryAccount.clicked.connect(self.pbQryAccountClicked)
        # self.pbQryOrder.clicked.connect(self.pbQryOrderClicked)
        # self.pbDeleteOrder.clicked.connect(self.pbDeleteOrderClicked)
        # self.pbRefreshAccount.clicked.connect(self.pbRefreshAccount_clicked)
        # self.pbRefreshOrder.clicked.connect(self.pbRefreshOrder_clicked)
        # self.ee.register(self.event_type['account'], self.on_account)
        # self.ee.register(self.event_type['position'], self.on_position)
        # self.ee.register(self.event_type['position_detail'], self.on_position_detail)
        # self.ee.register(self.event_type['order'], self.on_order)
        # self.ee.register(self.event_type['trade'], self.on_trade)

    def on_account(self, event):
        data = event.data
        mdl = PandasModel(data)
        self.tvAccount.setModel(mdl)

    def on_order(self, event):
        data = event.data
        mdl = PandasModel(data)
        self.tvAllOrder.setModel(mdl)

    def on_position(self, event):
        data = event.data
        mdl = PandasModel(data)
        self.tvPosition.setModel(mdl)

    def on_position_detail(self, event):
        data = event.data
        mdl = PandasModel(data)
        self.tvPositionDetail.setModel(mdl)

    def on_trade(self, event):
        data = event.data
        mdl = PandasModel(data)
        self.tvTrade.setModel(mdl)

    def pbQryAccountClicked(self):
        """query account"""
        # 账户查询

        # 持仓查询

        # 持仓明细查询

        # 成交查询

    def pbQryOrderClicked(self):
        """query order 委托查询"""

    def pbDeleteOrderClicked(self):
        """ delete untraded order"""

    def pbRefreshOrder_clicked(self):
        """"""

    def pbRefreshAccount_clicked(self):
        """"""
        self.pbQryAccountClicked()

    def tw_cell_clicked(self, x):
        # sender = self.sender()
        # print(x)
        # print(x.__dict__)
        # if isinstance(sender, (TickMonitor, ActiveOrderMonitor, PositionMonitor)):
        symbol = x.__dict__['_data'].symbol
        if self.current_instrument_id != symbol:
            self.current_instrument_id = symbol
            self.instrument_changed.emit(self.current_instrument_id)
            self.on_instrument_changed(self.current_instrument_id)


from vnpy.ui.frm_order import Ui_FrameOrder


class OrderPad(QtWidgets.QFrame, Ui_FrameOrder):
    """下单面板"""

    # ToDo 当持仓或持仓明细发来平仓信号时要处理
    # todo 测试对大期所、郑期所平昨指令的反应
    #  委托发送信号, 由交易管理接收（或者订阅)
    send_park_order_signal = QtCore.pyqtSignal(object)

    def __init__(self, me=None, ee=None, parent=None):
        super(OrderPad, self).__init__()
        self.setupUi(self)

        self.me = me
        self.ee = ee
        self.parent = parent
        self.symbol = ''
        self.vt_symbol = ''
        self.volume_multiple = 10
        self.price_tick = 1.0
        self.exchange_id = ''
        self.exchange = ''
        self.price_digits = 0

        self.price = 0
        self.contracts = {} if not me else me.get_all_contracts()
        self.instrument_info = {v.symbol: v for v in self.contracts}

        if self.parent:
            self.parent.instrument_changed.connect(self.on_instrument_changed)

        if self.ee:
            self.ee.register(EVENT_TICK, self.update_data)

        # 设置下单窗口始终处于前端
        self.setWindowFlags(QtCore.Qt.WindowFlags.WindowStaysOnTopHint)
        self._register_widget_event()

        self.order_ok = True
        self.park_order = False

    def _register_widget_event(self):
        self.pushButtonOrder.clicked.connect(self.order_clicked)
        self.pushButtonPriceType.clicked.connect(self.pbPriceType_clicked)
        self.spinBoxPrice.valueChanged.connect(self.spinBoxPrice_valueChanged)
        self.pushButtonBuy.clicked.connect(self.buy_clicked)
        self.pushButtonSell.clicked.connect(self.sell_clicked)
        self.pushButtonShort.clicked.connect(self.short_clicked)
        self.pushButtonCover.clicked.connect(self.cover_clicked)

        self.comboBoxSymbol.focusOutEvent = self.comboBoxSymbol_focusOutEvent

        self.comboBoxSymbol.setEditable(True)
        self.comboBoxSymbol.addItems([k for k in self.instrument_info.keys()])

    def comboBoxSymbol_focusOutEvent(self, e):
        iid = self.comboBoxSymbol.currentText()

        if iid in self.instrument_info.keys():
            ii = self.instrument_info.get(iid, {})
            # print(iid)
            if ii:
                self.symbol = iid
                self.price_tick = ii.get('pricetick', ii.get('PriceTick', self.price_tick))
                self.exchange_id = ii.get('ExchangeID', self.exchange_id)
                self.exchange = ii.get('exchange')
                self.spinBoxPrice.setSingleStep(self.price_tick)
                # print(self.price_tick)
                # if self.parent:
                #     self.spinBoxPrice.setValue(self.parent.last_price)

    @QtCore.pyqtSlot(str)
    def on_instrument_changed(self, iid: str):
        # self.lineEditSymbol.setText(iid)
        # print(iid)
        if iid.__contains__('.'):  # 此时的IID是一 vt_symbol: symbol.Exchange
            sbl, xcg = iid.split('.')
            if self.symbol != sbl:
                self.symbol = sbl
                self.exchange = xcg
                self.vt_symbol = iid
                self.price_tick = self.me.get_price_tick(self.vt_symbol)
                self.price_digits = get_digits(self.price_tick)
                self.comboBoxSymbol.setEditable(True)
                self.comboBoxSymbol.setEditText(sbl)
                self.spinBoxPrice.setSingleStep(self.price_tick)
            return

        if self.symbol != iid:
            self.symbol = iid
            self.exchange = self.me.get_contract_by_symbol(iid).__dict__.get('exchange', '')
            self.vt_symbol = '.'.join([self.symbol, self.exchange.value])
            self.price_tick = self.me.get_price_tick(self.vt_symbol)
            self.comboBoxSymbol.setEditable(True)
            self.comboBoxSymbol.setEditText(iid)
            self.spinBoxPrice.setSingleStep(self.price_tick)

    def buy_clicked(self):
        """开多"""
        symbol = self.comboBoxSymbol.currentText()
        price = self.spinBoxPrice.value()
        vol = self.spinBoxVol.value()
        data = OrderRequest(
            price=price,
            volume=vol,
            exchange=self.exchange,
            symbol=symbol,
            order_type=OrderType.LIMIT,
            direction=Direction.LONG,
            offset=Offset.OPEN)
        # msg = f'交易标的：{data.symbol}，价格：{data.price}，手数：{data.volume}，开多'
        # if not (QMessageBox.information(self, '交易信息', msg, QMessageBox.Ok | QMessageBox.Cancel) == QMessageBox.Ok):
        #     return
        self._send_order(data)

    def short_clicked(self):
        """开空"""
        symbol = self.comboBoxSymbol.currentText()
        price = self.spinBoxPrice.value()
        vol = self.spinBoxVol.value()
        data = OrderRequest(
            price=price,
            volume=vol,
            symbol=symbol,
            exchange=self.exchange,
            order_type=OrderType.LIMIT,
            direction=Direction.SHORT,
            offset=Offset.OPEN)
        # msg = f'交易标的：{data.symbol}，价格：{data.price}，手数：{data.volume}，开空'
        # if not (QMessageBox.information(self, '交易信息', msg, QMessageBox.Ok | QMessageBox.Cancel) == QMessageBox.Ok):
        #     return
        self._send_order(data)

    def sell_clicked(self):
        """平多"""
        symbol = self.comboBoxSymbol.currentText()
        price = self.spinBoxPrice.value()
        vol = self.spinBoxVol.value()
        data = OrderRequest(
            price=price,
            volume=vol,
            symbol=symbol,
            exchange=self.exchange,
            order_type=OrderType.LIMIT,
            direction=Direction.SHORT,
            offset=Offset.CLOSE)
        # msg = f'交易标的：{data.symbol}，价格：{data.price}，手数：{data.volume}，平多'
        # if not (QMessageBox.information(self, '交易信息', msg, QMessageBox.Ok | QMessageBox.Cancel) == QMessageBox.Ok):
        #     return
        self._send_order(data)

    def cover_clicked(self):
        """平空"""
        symbol = self.comboBoxSymbol.currentText()
        price = self.spinBoxPrice.value()
        vol = self.spinBoxVol.value()
        data = OrderRequest(
            price=price,
            volume=vol,
            symbol=symbol,
            exchange=self.exchange,
            order_type=OrderType.LIMIT,
            direction=Direction.LONG,
            offset=Offset.CLOSE)
        # msg = f'交易标的：{data.symbol}，价格：{data.price}，手数：{data.volume}，平空'
        # if not (QMessageBox.information(self, '交易信息', msg, QMessageBox.Ok | QMessageBox.Cancel) == QMessageBox.Ok):
        #     return
        self._send_order(data)

    def spinBoxPrice_valueChanged(self, e):
        # print(e)
        value = self.spinBoxPrice.value()
        # print(value, str(value))
        self.pushButtonBuy.setText(str(value) + '\n' + '开多')
        self.pushButtonShort.setText(str(value) + '\n' + '开空')
        self.pushButtonSell.setText(str(value) + '\n' + '平多')
        self.pushButtonCover.setText(str(value) + '\n' + '平空')

    def update_data(self, tick_event):
        """
        :param tick:
        :return:
        """
        tick = tick_event.data
        if tick.symbol not in [self.symbol, self.vt_symbol]:
            return
        sap1 = f'{tick.ask_price_1:.{self.price_digits}f}' if self.price_digits > 0 else f'{tick.ask_price_1}'
        sbp1 = f'{tick.bid_price_1:.{self.price_digits}f}' if self.price_digits > 0 else f'{tick.bid_price_1}'
        slup = f'{tick.limit_up:.{self.price_digits}f}' if self.price_digits > 0 else f'{tick.limit_up}'
        sldown = f'{tick.limit_down:.{self.price_digits}f}' if self.price_digits > 0 else f'{tick.limit_down}'

        if self.pushButtonPriceType.text() == '市价':
            self.spinBoxPrice.setValue(
                tick.last_price)  # setSpecialValueText(f'{tick.last_price:.{self.price_digits}f}')
            self.pushButtonBuy.setText(f'{sap1}\n开多')
            self.pushButtonShort.setText(f'{sbp1}\n开空')
            self.pushButtonSell.setText(f'{sap1}\n平多')
            self.pushButtonCover.setText(f'{sbp1}\n平空')

        self.labelSell.setText(f'卖:{sap1}/{tick.ask_volume_1}')
        self.labelBuy.setText(f'买:{sbp1}/{tick.bid_volume_1}')
        self.labelUpper.setText(f'涨停:{slup} 最高：{tick.high_price:.{self.price_digits}f}')
        self.labelLower.setText(f'跌停:{sldown} 最低：{tick.low_price:.{self.price_digits}f}')
        self.labelUpDown.setText(f'涨跌：{tick.last_price - tick.pre_close:.{self.price_digits}f} ')

    def order_clicked(self):
        """下单"""
        # if not self.symbol:
        #     return
        symbol = self.comboBoxSymbol.currentText()
        # if self.quote_event:
        #     self.quote_event.unregister(EVENT_TICK, self.update_data)

        price = self.spinBoxPrice.value()
        vol = self.spinBoxVol.value()
        # if self.radioButtonSpeculation.isChecked():
        #     HedgeFlag = defineDict['THOST_FTDC_HF_Speculation']
        if self.radioButtonBuy.isChecked():
            direction = Direction.LONG
        else:
            direction = Direction.SHORT
        if self.radioButtonOpen.isChecked():
            offset = Offset.OPEN
        elif self.radioButtonCloseToday.isChecked():
            offset = Offset.CLOSETODAY
        elif self.radioButtonCloseYesterday.isChecked():
            offset = Offset.CLOSEYESTERDAY
        else:
            offset = Offset.CLOSE
        order_req = OrderRequest(price=price,
                                 volume=vol,
                                 symbol=symbol,
                                 exchange=self.exchange,
                                 offset=offset,
                                 direction=direction,
                                 order_type=OrderType.LIMIT)
        self._send_order(order_req)

    def _send_order(self, data):
        self.park_order = self.cbPark.isChecked()
        if self.park_order:
            self.send_park_order_signal.emit(data)
            return

        msg = f'交易标的：{data.symbol}，价格：{data.price}，手数：{data.volume}，开平：{data.offset}，多空：{data.direction}'
        if QtWidgets.QMessageBox.information(self, '交易信息', msg,
                                             QtWidgets.QMessageBox.StandardButtons.Ok |
                                             QtWidgets.QMessageBox.StandardButtons.Cancel) == \
                QtWidgets.QMessageBox.StandardButtons.Ok:
            gateway_name = 'CTP'
            self.me.send_order(data, gateway_name)
            # if self.ee:
            #     # print(orderReq.__dict__)
            #     evnt = Event()
            #     evnt.event_type = EVENT_REQ_ORDER_INSERT
            #     evnt.dict_['data'] = data
            #     self.ee.put(evnt)
            # self.sendOrderSignal.emit(data.__dict__)

    def pbPriceType_clicked(self):
        """价格来源点击事件"""
        if self.pushButtonPriceType.text() == '指定价':
            self.pushButtonPriceType.setText('市价')
            # if self.quote_event:
            #     self.quote_event.register(EVENT_TICK, self.update_data)
        else:
            self.pushButtonPriceType.setText('指定价')
            # if self.quote_event:
            #     self.quote_event.unregister(EVENT_TICK, self.update_data)
